/*
 * Copyright (c) 2007 jNetX.
 * http://www.jnetx.com
 * All rights reserved.
 *
 * This software is the confidential and proprietary information of
 * jNetX. You shall not disclose such Confidential Information and
 * shall use it only in accordance with the terms of the license
 * agreement you entered into with jNetX.
 *
 * $Id$
 */
package com.jnetx.javax.sip.address;

import com.jnetx.javax.sip.util.ByteArrayUtil;

import javax.sip.InvalidArgumentException;
import javax.sip.address.SipURI;
import java.text.ParseException;

/**
 * @author <a href="mailto:dparhonin@jnetx.ru">Dmitry Parhonin</a>
 * @version $Revision$
 */
public class SipURIImpl extends URIBase implements SipURI {
    private String user;
    private String userPassword;
    private String host;
    private Integer port;

    private final static SipURIParser parser = new SipURIParser();

    protected URIParser getParser() {
        return parser;
    }

    public SipURIImpl() {
        super("sip");
    }

    public SipURIImpl(String user, String host) {
        super("sip");
        this.user = user;
        this.host = host;
    }

    public Object clone() {
        final SipURIImpl uri = new SipURIImpl();
        uri.cloneInternals(this);
        if (parsed) {
            uri.user = this.user;
            uri.userPassword = this.userPassword;
            uri.host = this.host;
            uri.port = this.port;
        }
        return uri;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof SipURIImpl) {
            final SipURIImpl uri = (SipURIImpl) obj;
            if (!uri.parsed && !parsed)
                return ByteArrayUtil.equals(this.data, this.start, this.end, uri.data, uri.start, uri.end);
            else if (!uri.parsed)
                uri.doParse();
            else if (!parsed)
                doParse();
            return this == uri || (super.equals(uri) && checkEquality(this.user, uri.user) &&
                    checkEquality(this.userPassword, uri.userPassword) &&
                    checkEquality(this.host, uri.host) &&
                    this.port == uri.port);
        } else
            return false;
    }

    @Override
    public String getScheme() {
        parse();
        return super.getScheme();
    }

    /**
     * This method determines if this is a URI with a scheme of "sip" or "sips".
     * It always returns <code>true</code> for SipURIImpl class.
     *
     * @return <code>true</code> if the scheme is "sip" or "sips", false otherwise.
     */
    public final boolean isSipURI() {
        return true;
    }

    /**
     * Sets the user of SipURI. The identifier of a particular resource at
     * the host being addressed. The user and the user password including the
     * '@' sign make up the user-info.
     *
     * @param user the new String value of the user.
     * @throws java.text.ParseException which signals that an error has been reached
     *                                  unexpectedly while parsing the user value.
     */
    public void setUser(String user) throws ParseException {
        invalidateStringValue();
        this.user = user;
    }

    /**
     * Returns the user part of this SipURI.
     *
     * @return the user part of this SipURI, this value may be null.
     */
    public String getUser() {
        parse();
        return user;
    }

    /**
     * Sets the user password associated with the user of SipURI. While the SIP and
     * SIPS URI syntax allows this field to be present, its use is NOT
     * RECOMMENDED, because the passing of authentication information in clear
     * text (such as URIs) has proven to be a security risk in almost every
     * case where it has been used. The user password and the user including
     * the @ sign make up the user-info.
     *
     * @param userPassword the new String value of the user password.
     * @throws java.text.ParseException which signals that an error has been reached
     *                                  unexpectedly while parsing the userPassword value.
     */
    public void setUserPassword(String userPassword) throws ParseException {
        invalidateStringValue();
        this.userPassword = userPassword;
    }

    /**
     * Gets user password of SipURI, or null if it is not set.
     *
     * @return the user password of this SipURI.
     */
    public String getUserPassword() {
        parse();
        return userPassword;
    }

    /**
     * Returns true if this SipURI is secure i.e. if this SipURI represents a
     * sips URI. A sip URI returns false.
     *
     * @return <code>true</code> if this SipURI represents a sips URI, and
     *         <code>false</code> if it represents a sip URI.
     */
    public boolean isSecure() {
        return getScheme().equals("sips");
    }

    /**
     * Sets the scheme of this URI to sip or sips depending on whether the
     * argument is <code>true</code> or <code>false</code>. The default value is <code>false</code>.
     *
     * @param secure the boolean value indicating if the SipURI is secure.
     */
    public void setSecure(boolean secure) {
        invalidateStringValue();
        if (secure)
            setScheme("sips");
        else
            setScheme("sip");
    }

    /**
     * Set the host part of this SipURI to the newly supplied <code>host</code>
     * parameter.
     *
     * @param host the new interger value of the host of this SipURI.
     * @throws java.text.ParseException which signals that an error has been reached
     *                                  unexpectedly while parsing the host value.
     */
    public void setHost(String host) throws ParseException {
        invalidateStringValue();
        this.host = host;
    }

    /**
     * Returns the host part of this SipURI.
     *
     * @return the host part of this SipURI
     */
    public String getHost() {
        parse();
        return host;
    }

    /**
     * Set the port part of this SipURI to the newly supplied port
     * parameter.
     *
     * @param port the new integer value of the port of this SipURI.
     */
    public void setPort(int port) {
        invalidateStringValue();
        this.port = port;
    }

    /**
     * Returns the port part of this SipURI.
     *
     * @return the port part of this SipURI.
     */
    public int getPort() {
        parse();
        return port == null ? 5060 : port;
    }

    /**
     * Removes the port part of this SipURI. If no port is specified the
     * stack will assume the default port.
     */
    public void removePort() {
        invalidateStringValue();
        port = null;
    }

    /**
     * Returns the value of the "transport" parameter, or null if this is not
     * set. This is equivalent to getParameter("transport").
     *
     * @return the transport paramter of the SipURI.
     */
    public String getTransportParam() {
        return getParameter("transport");
    }

    /**
     * Sets the value of the "transport" parameter. This parameter specifies
     * which transport protocol to use for sending requests and responses to
     * this entity. The following values are defined: "udp", "tcp", "sctp",
     * "tls", but other values may be used also. This method is equivalent to
     * setParameter("transport", transport). Transport parameter constants
     * are defined in the {@link javax.sip.ListeningPoint}.
     *
     * @param transport new value for the "transport" parameter.
     * @throws java.text.ParseException which signals that an error has been reached
     *                                  unexpectedly while parsing the transport value.
     */
    public void setTransportParam(String transport) throws ParseException {
        setParameter("transport", transport);
    }

    /**
     * Returns the value of the "ttl" parameter, or -1 if this is not set.
     * This method is equivalent to getParameter("ttl").
     *
     * @return the value of the <code>ttl</code> parameter.
     */
    public int getTTLParam() {
        final String ttlParam = getParameter("ttl");
        int ttl = -1;
        try {
            if (ttlParam != null) ttl = Integer.parseInt(ttlParam);
        } catch (Exception e) {
            //just ignore it
        }
        return ttl;
    }

    /**
     * Sets the value of the <code>ttl</code> parameter. The ttl parameter
     * specifies the time-to-live value when packets are sent using UDP
     * multicast. This is equivalent to setParameter("ttl", ttl).
     *
     * @param ttl new value of the <code>ttl</code> parameter.
     * @throws javax.sip.InvalidArgumentException
     *          if supplied value is less than zero,
     *          excluding -1 the default not set value.
     */
    public void setTTLParam(int ttl) throws InvalidArgumentException {
        if (ttl < -1)
            throw new InvalidArgumentException();
        if (ttl == -1)
            removeParameter("ttl");
        else {
            final String ttlValue;
            try {
                ttlValue = Integer.toString(ttl);
                setParameter("ttl", ttlValue);
            } catch (Exception e) {
                throw new InvalidArgumentException("The TTL parameter is wrong!", e);
            }
        }
    }

    /**
     * Returns the value of the <code>method</code> parameter, or null if this
     * is not set. This is equivalent to getParameter("method").
     *
     * @return the value of the <code>method</code> parameter.
     */
    public String getMethodParam() {
        return getParameter("method");
    }

    /**
     * Sets the value of the <code>method</code> parameter. This specifies
     * which SIP method to use in requests directed at this URI. This is
     * equivalent to setParameter("method", method).
     *
     * @param method new value String value of the method parameter.
     * @throws java.text.ParseException which signals that an error has been reached
     *                                  unexpectedly while parsing the method value.
     */
    public void setMethodParam(String method) throws ParseException {
        setParameter("method", method);
    }

    /**
     * Sets the value of the user parameter. The user URI parameter exists to
     * distinguish telephone numbers from user names that happen to look like
     * telephone numbers.  This is equivalent to setParameter("user", user).
     *
     * @param userParam new value String value of the method parameter.
     * @throws java.text.ParseException which signals that an error has been reached
     *                                  unexpectedly while parsing the userParam value.
     */
    public void setUserParam(String userParam) throws ParseException {
        setParameter("user", userParam);
    }

    /**
     * Returns the value of the <code>userParam</code>, or null if this is not
     * set.
     * This is equivalent to getParameter("user").
     *
     * @return the value of the <code>userParam</code> of the SipURI.
     */
    public String getUserParam() {
        return getParameter("userParam");
    }

    /**
     * Returns the value of the <code>maddr</code> parameter, or null if this
     * is not set. This is equivalent to getParameter("maddr").
     *
     * @return the value of the <code>maddr</code> parameter.
     */
    public String getMAddrParam() {
        return getParameter("maddr");
    }

    /**
     * Sets the value of the <code>maddr</code> parameter of this SipURI. The
     * maddr parameter indicates the server address to be contacted for this
     * user, overriding any address derived from the host field. This is
     * equivalent to setParameter("maddr", maddr).
     *
     * @param mAddr new value of the <code>maddr</code> parameter.
     * @throws java.text.ParseException which signals that an error has been reached
     *                                  unexpectedly while parsing the mAddr value.
     */
    public void setMAddrParam(String mAddr) throws ParseException {
        setParameter("maddr", mAddr);
    }

    /**
     * Returns whether the the <code>lr</code> parameter is set. This is
     * equivalent to hasParameter("lr"). This interface has no getLrParam as
     * RFC3261 does not specify any values for the "lr" paramater.
     *
     * @return <code>true</code> if the "lr" parameter is set, <code>false</code> otherwise.
     */
    public boolean hasLrParam() {
        return hasParameter("lr");
    }

    /**
     * Sets the value of the <code>lr</code> parameter of this SipURI. The lr
     * parameter, when present, indicates that the element responsible for
     * this resource implements the routing mechanisms specified in RFC 3261.
     * This parameter will be used in the URIs proxies place in the
     * Record-Route header field values, and may appear in the URIs in a
     * pre-existing route set.
     */
    public void setLrParam() {
        try {
            setParameter("lr", null);
        } catch (ParseException e) {
            //just ignore it
        }
    }

    protected static SipURIImpl createLazily(String rawURI) {
        return createLazily(rawURI.getBytes(), 0, rawURI.length());
    }

    protected static SipURIImpl createLazily(byte[] rawURI, int start, int end) {
        final SipURIImpl uri = new SipURIImpl();
        uri.data = rawURI;
        uri.start = start;
        uri.end = end;
        uri.parsed = false;
        return uri;
    }

    protected String buildStringValue() {
        final StringBuilder sb = new StringBuilder();
        sb.append(getScheme()).append(':');
        if (user != null && !user.equals("")) {
            sb.append(user);
            if (userPassword != null && !userPassword.equals(""))
                sb.append(':').append(userPassword);
            sb.append('@');
        }
        sb.append(host);
        if (port != null) sb.append(':').append(port);
        sb.append(getParametersAsString(";"));
        sb.append(getHeadersAsString());
        return sb.toString();
    }
}
/*
 * $Log$
 */